/**
 * 
 */
package com.module.bp.engine;

import com.dalonedrau.jogl.engine.GameApplet;
import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.watchable.Watchable;
import com.dalonedrau.watchable.Watcher;
import com.module.bp.event.base.BaseEvent;
import com.module.bp.event.base.Event;
import com.module.bp.event.base.GameOverEvent;
import com.module.bp.event.numbered.EventE002;
import com.module.bp.player.BPConsts;
import com.module.bp.player.BPParty;
import com.module.bp.view.OverworldView;

/**
 * @author Donald
 */
public class Turn implements Watchable {
	/** the one and only instance of the <code>Turn</code> class. */
	private static Turn		instance;
	/** the phase when the player decides an action to take. */
	public static final int	PHASE_ACTION		= 1;	// ("Dawn"),
	/** phase where player decides evening action. */
	public static final int	PHASE_EVENING		= 4;	// ("Dusk");
	/** phase where final events for the day occur. */
	public static final int	PHASE_MIDNIGHT		= 6;	// ("Midnight");
	/** the phase where the player's action and random events are processed. */
	public static final int	PHASE_POST_ACTION	= 2;	// ("Noon"),
	/** phase where evening actions and post-meal events occur. */
	public static final int	PHASE_POST_EVENING	= 5;	// ("Dusk");
	/** the phase before the player decides on a daily action. */
	public static final int	PHASE_PRE_ACTION	= 0;	// ("Dawn"),
	/** phase where pre-evening meal events occur. */
	public static final int	PHASE_PRE_EVENING	= 3;	// ("Dusk"),
	/**
	 * Gives access to the singleton instance of {@link Turn}.
	 * @return {@link Turn}
	 */
	public static Turn getInstance() {
		if (Turn.instance == null) {
			Turn.instance = new Turn();
		}
		return Turn.instance;
	}
	/** the current phase. */
	private int			currentPhase	= 0;
	private static final int NUM_PHASES = 7;
	/** the float representing division by 7. */
	private final float	DIV7			= 0.1428571428571429f;
	/** the lists of {@link Event}s for each phase. */
	private Event[][]	events2;
	/** the total number of events for each phase. */
	private int[]		numEvents;
	/** the game's time track. */
	private int			timetrack		= 0;
	/** the list of {@link Watcher}s. */
	private Watcher[]	watchers		= new Watcher[0];
	/** Hidden constructor. */
	private Turn() {
		currentPhase = Turn.PHASE_PRE_ACTION;
		timetrack = 0;
		events2 = new Event[NUM_PHASES][0];
		numEvents = new int[NUM_PHASES];
	}
	/**
	 * Adds a {@link BaseEvent} to the current phase of the <code>Turn</code>.
	 * @param e the {@link BaseEvent} being added
	 */
	public void addEvent(final Event e) {
		this.addEvent(currentPhase, e);
	}

	/**
	 * Adds a {@link BaseEvent} to the current phase of the <code>Turn</code>.
	 * @param phase the phase when the {@link BaseEvent} is fired
	 * @param event the {@link BaseEvent} being added
	 */
	public void addEvent(final int phase, final Event event) {
		Event[] list = events2[phase];
		int currNum = numEvents[phase];
		if (list.length <= currNum + 1) {
			Event[] dest = new Event[currNum + 1];
			System.arraycopy(list, 0, dest, 0, list.length);
			list = dest;
			dest = null;
		}
		list[currNum] = event;
		events2[phase] = list;
		numEvents[phase] = currNum + 1;
		System.out.println("added event for " + currentPhase);
		System.out.println("total events: " + numEvents[currentPhase]);
	}
	/**
	 * Adds a list of {@link Event}s to occur after a specific {@link Event} for
	 * a specific phase of the {@link Turn}.
	 * @param phase the phase when the {@link Event} is fired
	 * @param afterEventId the id of the {@link Event} this new list of
	 *        {@link Event}s occurs after
	 * @param events the list of {@link Event}s
	 * @throws Exception if an error occurs
	 */
	public void addEvent(final int phase, final int afterEventId,
			final Event... events) throws Exception {
		// find the event.
		int index = -1;
		Event[] list = events2[phase];
		for (int i = 0; i < list.length; i++) {
			if (list[i] != null
					&& list[i].getId() == afterEventId) {
				// found it!!
				index = i;
				break;
			}
		}
		if (index == -1) {
			throw new Exception("Invalid index " + afterEventId);
		}
		System.out.println("adding " + events.length + " at index " + index);
		// copy all events before the event into a new array
		Event[] dest = new Event[list.length + events.length];
		System.arraycopy(list, 0, dest, 0, index + 1);
		// copy the new events into dest
		System.arraycopy(events, 0, dest, index + 1, events.length);
		// copy remaining events into dest array
		System.arraycopy(list, index + 1, dest, index + 1 + events.length,
				list.length - (index + 1));
		events2[phase] = dest;
		dest = null;
		for (int i = 0; i < events2[phase].length; i++) {
			if (events2[phase][i] != null) {
				System.out
						.println(events2[phase][i].getClass().getSimpleName());
			}
		}
		numEvents[phase] = numEvents[phase] + events.length;
	}
	/**
	 * Adds a list of {@link Event}s to occur after a specific {@link Event} for
	 * the current phase of the {@link Turn}.
	 * @param afterEventId the id of the {@link Event} this new list of
	 *        {@link Event}s occurs after
	 * @param events the list of {@link Event}s
	 * @throws Exception if an error occurs
	 */
	public void addEventAfterId(final int afterEventId, final Event... events)
			throws Exception {
		this.addEvent(currentPhase, afterEventId, events);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addWatcher(final Watcher watcher) {
		Watcher[] dest = new Watcher[watchers.length + 1];
		System.arraycopy(watchers, 0, dest, 0, watchers.length);
		dest[watchers.length] = watcher;
		watchers = dest;
		dest = null;
	}
	/** Advances the current phase. */
	public void advancePhase() {
		currentPhase++;
		if (currentPhase > Turn.PHASE_MIDNIGHT) {
			currentPhase = Turn.PHASE_PRE_ACTION;
			BPParty.getInstance().resetNumberOfMoves();
			timetrack++;
			if (timetrack >= 70) {
				this.addEvent(new GameOverEvent());
			}
		}
		switch (currentPhase) {
		case Turn.PHASE_PRE_ACTION:
			// disallow action menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// disallow evening menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
		case Turn.PHASE_ACTION:
			// allow action menu
			OverworldView.getInstance().assignFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// disallow evening menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
			break;
		case Turn.PHASE_POST_ACTION:
			// disallow action menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// disallow evening menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
			break;
		case Turn.PHASE_PRE_EVENING:
			this.addEvent(new EventE002());
			OverworldView.getInstance().setMapMode(BPConsts.OVM_FREE_MODE);
			// disallow action menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// disallow evening menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
			break;
		case Turn.PHASE_EVENING:
			// disallow action menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// allow evening menu
			OverworldView.getInstance().assignFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
			break;
		case Turn.PHASE_POST_EVENING:
		case Turn.PHASE_MIDNIGHT:
			// disallow action menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU);
			// disallow evening menu
			OverworldView.getInstance().removeFlag(
					OverworldView.OWV_ALLOW_EVENING_MENU);
			break;
		}
		System.out.println("PHASE " + currentPhase);
		notifyWatchers();
	}
	/**
	 * Clears all events for a specific phase.
	 * @param phase the phase being cleared
	 */
	public void clearEvents(final int phase) {
		Event[] list = events2[phase];
		for (int i = 0; i < list.length; i++) {
			list[i] = null;
		}
		numEvents[phase] = 0;
	}
	/**
	 * Determines if there is an event ready to fire for the current phase.
	 * @return true if there is an event that can be fired for the current
	 *         phase; false otherwise
	 */
	public boolean currentPhaseHasEventToFire() {
		boolean ready = false;
		if (numEvents[currentPhase] == 0) {
			ready = false;
		} else {
			Event[] list = events2[currentPhase];
			// if we can go through events list and find one event to
			// fire, then return true
			for (int i = 0; i < list.length; i++) {
				Event e = list[i];
				if (e == null) {
					continue;
				}
				if (e.wasFired()
						&& e.isResolved()) {
					System.out.println("*****REMOVING EVENT " + e);
					// the last event was resolved
					// remove the event from the list
					list[i] = null;
					// reduce the number of events
					numEvents[currentPhase] = numEvents[currentPhase] - 1;
					continue;
				} else if (e.wasFired() && !e.isResolved()) {
					// waiting on event to finish
					System.out.println("waiting for "
							+ e.getClass().getSimpleName() + " to finish");
					break;
				}
				if (!e.wasFired()) {
					System.out.println("found event "
							+ e.getClass().getSimpleName() + " to fire");

					ready = true;
					break;
				}
			}
		}
		return ready;
	}
	/**
	 * Determines if all events for the current phase were completed.
	 * @return true if there are no more events; false otherwise
	 */
	public boolean eventsCompletedForCurrentPhase() {
		boolean complete = false;
		if (numEvents[currentPhase] == 0) {
			complete = true;
		} else {
			// set return value to true
			complete = true;
			Event[] list = events2[currentPhase];
			// if we can go through events list and not find one event that's
			// unresolved, then all events were completed
			for (int i = 0; i < list.length; i++) {
				Event e = list[i];
				if (e == null) {
					continue;
				}
				if (e.wasFired()
						&& e.isResolved()) {
					// the last event was resolved
					// remove the event from the list
					list[i] = null;
					// reduce the number of events
					numEvents[currentPhase] = numEvents[currentPhase] - 1;
				} else {
					// last event was not resolved
					complete = false;
					break;
				}
			}
		}
		return complete;
	}
	/** Fires the next available event. */
	public void fireNextEvent() {
		Event[] list = events2[currentPhase];
		for (int i = 0; i < list.length; i++) {
			Event e = list[i];
			if (e == null) {
				continue;
			}
			if (!e.wasFired()) {
				try {
					System.out.println("**********FIREING " + e);
					e.fire();
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
				break;
			} else {
				System.out
						.println("****************"
								+ e.getClass().getSimpleName()
								+ " fired, not resolved");
			}
		}
	}
	/**
	 * Gets the current phase.
	 * @return {@link int}
	 */
	public int getCurrentPhase() {
		return currentPhase;
	}
	/**
	 * Gets the {@link String} for rendering the current day.
	 * @return {@link String}
	 */
	public String getDayString() {
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		sb.append("DAY ");
		sb.append(timetrack % GlobalConsts.INT_007 + 1);
		sb.append(" WEEK ");
		sb.append((int) (timetrack * DIV7) + 1);
		String s = sb.toString();
		sb.returnToPool();
		return s;
	}
	/**
	 * Gets the next phase.
	 * @return <code>int</code>
	 */
	public int getNextPhase() {
		int next = currentPhase + 1;
		if (next > Turn.PHASE_MIDNIGHT) {
			next = Turn.PHASE_PRE_ACTION;
		}
		return next;
	}
	/**
	 * Gets the {@link String} for rendering the current phase.
	 * @return {@link String}
	 */
	public String getPhaseString() {
		String s = null;
		switch (currentPhase) {
		case Turn.PHASE_PRE_ACTION:
		case Turn.PHASE_ACTION:
			s = "Dawn";
			break;
		case Turn.PHASE_POST_ACTION:
			s = "Noon";
			break;
		case Turn.PHASE_PRE_EVENING:
		case Turn.PHASE_EVENING:
		case Turn.PHASE_POST_EVENING:
			s = "Dusk";
			break;
		case Turn.PHASE_MIDNIGHT:
			s = "Midnight";
			break;
		default:
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Turn set to invalid phase: ");
			sb.append(currentPhase);
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			JOGLErrorHandler.getInstance().fatalError(ex);
			break;
		}
		return s;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void notifyWatchers() {
		for (int i = 0; i < watchers.length; i++) {
			watchers[i].watchUpdated(this);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeWatcher(final Watcher watcher) {
		Watcher[] dest = new Watcher[watchers.length - 1];
		for (int i = 0, j = 0; i < watchers.length; i++) {
			if (!watchers[i].equals(watcher)) {
				dest[j] = watchers[i];
				j++;
			}
		}
		watchers = dest;
		dest = null;
	}
}
